(function() {

    var
    $u = juliet.using({
        array : 'juliet.core.array-0.1',
        object : 'juliet.core.object-0.1',
        uidGenerator : 'juliet.core.uidGenerator-0.1',
        validationResult : 'juliet.validation.validationResult-0.2',
        validationEvent : 'juliet.validation.validationEvent-0.2',
        Observer : 'juliet.core.Observer-0.1',
        InputHelper : 'juliet.validation.InputHelper-0.2'
    }),
    ve = $u('validationEvent'),
    vr = $u('validationResult');

    var ValidatorsChainClass = function() {

        this.options = null;

        this.uid = null;

        this.validators = {

            first : null,

            last : null,

            inProgress : null,

            failed : null,

            list : { }

        };

        this.observer = juliet.createObject($u('Observer'));

        this.inputHelper = null;

        this.manager = null;

        this.validationCache = { value : null, result : vr.UNKNOWN };

        this.loadValidatorClass = function(name) {
            var localUsing = juliet.using({ validator : 'juliet.validation.validators.' + name + '-0.2' });
            return localUsing('validator');
        },

        this.prepareValidatorOptions = function(options) {
            options = options || { };
            options.chain = this;

            return options;
        },

        this.addValidator = function(validatorData) {
            var
            validatorClass = this.loadValidatorClass(validatorData.name),
            listItem = {
                object : juliet.createObject(
                    validatorClass,
                    this.prepareValidatorOptions(validatorData.options)
                ),
                prev : this.validators.last,
                next : null
            };

            if (this.validators.first == null) {
                this.validators.first = listItem;
            }

            if (this.validators.last != null) {
                this.validators.last.next = listItem;
            }

            this.validators.last = listItem;
            this.validators.list[listItem.object.uid] = listItem;
        };

        this.addValidators = function(validatorsData) {
            $u('array').each(validatorsData, function(item) {
                this.addValidator(item);
            }, this);
        };

        this.validate = function(o) {
            var
            helper = this.inputHelper,
            cache = this.validationCache;

            this.runValidationCycle(o);

            cache.value = helper.currentValue;
            cache.result = null;

            if (ve.isIn(o.event, ve.MANAGER | ve.CHANGE | ve.CONTINUE)) {
                if (this.validators.failed != null) this.showError();
                else this.hideError();
            }

            this.observer.notify('validated', this);
        };

        this.runValidationCycle = function(o) {
            var v = this.validators;

            v.failed = null;
            v.inProgress = o.validatorListItem || v.first;

            while (v.inProgress !== null) {
                var validator = v.inProgress.object;

                if (!validator.isSupportedEvent(o.event)) return;
                validator.validate(this.inputHelper.currentValue);

                if (validator.getResult() == vr.FAILED) {
                    v.failed = v.inProgress;
                    return;
                } else if (validator.getResult() == vr.IN_PROGRESS) {
                    return;
                }

                v.inProgress = v.inProgress.next;
            }
        };

        this.getResult = function() {
            var cache = this.validationCache;

            if (cache.result == null) {
                var result = 0;

                $u('object').each(this.validators.list, function(item) {
                    result |= item.object.getResult();
                });

                cache.result = vr.simplify(result);
            }

            return cache.result;
        };

        this.showError = function() {
            this.observer.notify('show-error', this);
        };

        this.hideError = function() {
            this.observer.notify('hide-error', this);
        };

        this.reset = function() {
            $u('object').each(this.validators.list, function(item) {
                item.object.reset();
            });

            this.validators.failed = null;
            this.validators.inProgress = null;
            this.validationCache = { value : null, result : vr.UNKNOWN };
            this.hideError();
        };

        this.onInputEventHandler = function(event) {
            this.validate({ event : event });
        };

        this.subscribeOnEvents = function() {
            this.inputHelper.observer.subscribe('input-event', this.onInputEventHandler, this);
        };

        this.initialize = function(o) {
            this.options = o;
            this.uid = o.uid || $u('uidGenerator').getUid();
            this.inputHelper = juliet.createObject($u('InputHelper'), o.fieldSelector);
            this.manager = o.manager;

            this.addValidators(o.validators);
            this.subscribeOnEvents();
        };

    };

    juliet.registerModule('juliet.validation', 'ValidatorsChain-0.2', ValidatorsChainClass);

})();
